using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Diagnostics;
using System.IO.Ports;
using System.Runtime.InteropServices;
using EARTHLib;
using System.Collections;
using System.IO;


namespace BlimpControlPanel
{
    public partial class controlPanelForm : Form
    {
#if GOOGLE_EARTH_ENABLED
        ArrayList latLongAltArray;

        #region GOOGLE_EARTH_IMPORTS
        [DllImport("user32.dll")]
        static extern int SetParent(int hWndChild, int hWndParent);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public extern static bool SetWindowPos(int hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);

        readonly IntPtr HWND_TOP = new IntPtr(0);
        static readonly UInt32 SWP_SHOWWINDOW = 64;
        private googleEarthControl geCntrl;
        #endregion
#endif

        #region CONSTANT_DATA
        private const int DATA_VALID_INDEX = 0;
        private const int DATA_SATELLITES_INDEX = 1;
        private const int DATA_TIME_INDEX = 2;
        private const int DATA_DATE_INDEX = 3;
        private const int DATA_LATITUDE_INDEX = 4;
        private const int DATA_LONGITUDE_INDEX = 5;
        private const int DATA_ALTITUDE_INDEX = 6;
        private const int DATA_SPEED_INDEX = 7;
        private const int DATA_HEADING_INDEX = 8;
        #endregion

        private Thread serialPortThread = null;
        private Thread sendPortThread = null;
        private Thread readPortThread = null;
        private string lastCommandSent = "";
        protected Queue<string> messageQueue;
        private int lastReceivedIndex = -1;
        delegate void UpdateDataCallBack(string newline);

#if GOOGLE_EARTH_ENABLED
        private ApplicationGEClass ageClass;
#endif

        private void controlPanelForm_Load(object sender, EventArgs e)
        {
            toolStripStatusLabel1.Text = "Disconnected";

#if GOOGLE_EARTH_ENABLED
            latLongAltArray = new ArrayList();
            geCntrl.TestGetFeature();
#else
            tabControl1.TabPages.RemoveByKey(tpGoogleEarth.Name);
            tabControl1.Update();

#endif
            OpenLogFile();
        }

        protected override void OnClosing(CancelEventArgs e)
        {
            if (serialPort1.IsOpen )
                connectDisconnect();

            if (serialPortThread != null)
            {
                serialPortThread.Abort();
                serialPortThread.Join(3000);
            }

            if (sendPortThread != null)
            {
                sendPortThread.Abort();
                sendPortThread.Join(3000);
            }

            base.OnClosing(e);
        }

        protected override void OnFormClosed(FormClosedEventArgs e)
        {
#if GOOGLE_EARTH_ENABLED
            geCntrl.googleEarthControlClose();
#endif
            CloseLogFile();

            base.OnFormClosed(e);
        }

        EventWaitHandle ewh;

        /// <summary>
        /// 
        /// </summary>
        public controlPanelForm()
        {
            InitializeComponent();

            ewh = new EventWaitHandle(false, EventResetMode.AutoReset);
            writeQueue = new Queue<string>(100);

            motorControl1.writeCommand += new MotorControl.WriteCommandHandler(motorControl1_writeCommand);

            bgAltitude.Axis.Max = 2000;

#if GOOGLE_EARTH_ENABLED
            ageClass = new ApplicationGEClass();

            geCntrl = new googleEarthControl(ageClass, this.Handle, this.panelGoogleEarth.Width, this.panelGoogleEarth.Height);
            this.panelGoogleEarth.Controls.Add(geCntrl);
#endif
        }

        void motorControl1_writeCommand(string message)
        {
            writeQueue.Enqueue(message);
        }

        private void connectDisconnect()
        {
            try
            {
                ConnectDisconnectPort();
            }
            catch
            {
            }
        }

        private bool ConnectDisconnectPort()
        {
            try
            {
                if (serialPort1 == null)
                    return false;

                if (serialPort1.IsOpen)
                {
                    serialPort1.Close();
                    UpdateLabelsAndButtons(false);
                }
                else
                {
                    if (!SetupAndOpenSerialPort(serialPort1))
                    {
                        MessageBox.Show(this, "Failure to open Serial Port, Please check settings and try again", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                        return false;
                    }

                    curTime = 0;

                    UpdateLabelsAndButtons(true);
                    StartReadThread();
                    StartSendThread();
                }
            }
            catch
            {
            }

            return true;
        }

        private void StartSendThread()
        {
            if (serialPort1.IsOpen)
            {
                sendPortThread = new Thread(new ParameterizedThreadStart(sendPortWriteThread));
                sendPortThread.Start(serialPort1);
            }
        }

        private void StartReadThread()
        {
            if (serialPort1.IsOpen)
            {
                serialPortThread = new Thread(new ParameterizedThreadStart(serialPortReadThread));
                serialPortThread.Start(serialPort1);
            }
        }

        private void UpdateLabelsAndButtons(bool Connect)
        {
            if (Connect)
            {
                motorControl1.Enabled = true;
                toolStripStatusLabel1.Text = "Comm Port: " + serialPort1.PortName + " Connected: " + serialPort1.BaudRate.ToString() + ", " + serialPort1.Parity.ToString() + ", " + serialPort1.DataBits.ToString();
                tsbtnConnect.Text = "Disconnect";
                tsbtnConnect.Image = BlimpControlPanel.Properties.Resources.connect;
            }
            else
            {
                motorControl1.Enabled = false;
                toolStripStatusLabel1.Text = "Disconnected";
                tsbtnConnect.Text = "Connect";
                tsbtnConnect.Image = BlimpControlPanel.Properties.Resources.disconnect;
            }

            lastReceivedIndex = -1;
            motorControl1.ResetCommandStatusLabel();
        }

        private bool SetupAndOpenSerialPort(SerialPort sp)
        {
            if (SetDefaultSerialPort(sp))
            {
                sp.ReceivedBytesThreshold = 1;
                sp.Open();

                return sp.IsOpen;
            }

            return false;
        }

        private bool SetDefaultSerialPort(SerialPort sp)
        {
            string[] serialPorts = SerialPort.GetPortNames();

            foreach (string port in serialPorts)
            {
                if (sp == serialPort1 && port == Properties.Settings.Default.ComPort)
                {
                    sp.StopBits  = System.IO.Ports.StopBits.One;
                    sp.BaudRate  = Properties.Settings.Default.BaudRate;
                    sp.DataBits  = Properties.Settings.Default.DataBits;
                    sp.PortName  = Properties.Settings.Default.ComPort;
                    sp.Parity    = System.IO.Ports.Parity.None;
                    sp.Handshake = System.IO.Ports.Handshake.None;
                    return true;
                }
            }

            if (sp == serialPort1)
                MessageBox.Show("Error: " + Properties.Settings.Default.ComPort + " not found.\nPlease choose another port and try again.\n", "Comm Port Not Found", MessageBoxButtons.OK, MessageBoxIcon.Error);

            return false;
        }

        Queue<string> writeQueue;

        private void sendPortWriteThread(object sp)
        {
            if (sp == null)
                return;

            SerialPort spObject = (SerialPort)sp;

            try
            {
                while (spObject.IsOpen)
                {
                    System.Windows.Forms.Application.DoEvents();

                    while (spObject.IsOpen && writeQueue.Count > 0)
                        WriteSerialPort(spObject);

                    Thread.Sleep(100);
                }
            }
            catch
            {
                //MessageBox.Show("Read Thread Error Occurred: " + ex.Message);
                UpdateLabelsAndButtons(false);
            }
        }

        private void WriteSerialPort(SerialPort spObject)
        {
            byte count = 0;

            while(writeQueue.Count > 0)
            {
                motorControl1.Enabled = false;

                LastCommand = writeQueue.Dequeue();

                while(count < 3  && LastCommand != "")
                {
                    ewh.Reset();
                    serialPort1.WriteLine(LastCommand + "\n\r");
                    Debug.WriteLine("Sending Command: " + LastCommand);
                    ewh.WaitOne(1000, false);
                    count++;
                }

                motorControl1.Enabled = true;
            }

            if (LastCommand != "")
                motorControl1.SetCommandStatusLabel(false);

            LastCommand = "";

            

        }

        private void serialPortReadThread(object sp)
        {
            if (sp == null)
                return;

            SerialPort spObject = (SerialPort)sp;

            try
            {
                while (spObject.IsOpen)
                {
                    System.Windows.Forms.Application.DoEvents();

                    while (spObject.IsOpen && spObject.BytesToRead > 0)
                        ReadSerialPort(spObject);

                }
            }
            catch
            {
                //MessageBox.Show("Read Thread Error Occurred: " + ex.Message);
                UpdateLabelsAndButtons(false);
            }
        }

        private string LastCommand
        {
            set { lastCommandSent = value; }
            get { return lastCommandSent; }
        }


        private void ReadSerialPort(SerialPort sp)
        {
            if (sp != null && (!sp.IsOpen || sp.BytesToRead == 0))
                return;

            try
            {
                int bytes = sp.BytesToRead;
                byte[] readBytes = new byte[bytes];

                if (sp.IsOpen)
                {
                    System.Windows.Forms.Application.DoEvents();

                    string outString = sp.ReadLine();

                    if (outString.Length == 0)
                        return;

                    Debug.WriteLine("Read Line: " + outString + " Last Command: " + LastCommand + " Compare: " + lastCommandSent.CompareTo(outString).ToString());

                    if (LastCommand.Length > 0)
                    {
                        if (LastCommand.Length > 0 && (LastCommand.CompareTo(outString.Trim()) == 0))
                        {
                            ewh.Set();
                            Debug.WriteLine("Received Command");
                            LastCommand = "";
                            motorControl1.SetCommandStatusLabel(true);
                        }
                    }

                    this.UpdateData(outString);
                }
            }
            catch
            {
            }
        }

        private void UpdateData(string newline)
        {
            if (!this.IsDisposed && this.InvokeRequired)
            {
                UpdateDataCallBack updateDataCB = new UpdateDataCallBack(UpdateDataBoxes);
                this.Invoke(updateDataCB, new string[] { newline });
            }
            else
                UpdateDataBoxes(newline);
        }

        private void UpdateDataBoxes(string newLine)
        {
            if (!cbPauseData.Checked)
            {
                lbData.Items.Add(newLine.TrimEnd());
                lbData.SelectedIndex = lbData.Items.Count - 1;
            }

            //UpdateParameters(new string[] { newLine });
            SaveFileDialogKLM sfdk = new SaveFileDialogKLM();

            if (!sfdk.CheckSumValid(new string[] { newLine }))
                return;

            UpdateParameters(newLine.Split(new char[] { ',' }));

            if (tsbtnLogGPSData.Checked && pcDataValid.ParameterValue == "Yes")
                AddLineToLogFile(newLine);
        }

        private string filenamebase = @"C:\gpsLog";
        private string currentFilename = "";
        private StreamWriter sw = null;

        private void OpenLogFile()
        {
            if (sw == null)
            {
                currentFilename = filenamebase + "-" + DateTime.Now.Ticks + ".txt";

                sw = new StreamWriter(new FileStream(currentFilename, FileMode.Create));
            }
        }

        private void CloseLogFile()
        {
            if (sw != null)
                sw.Close();

            sw = null;
        }

        private void AddLineToLogFile(string newLine)
        {
            if (sw != null)
                sw.WriteLine(newLine);
        }

        private void UpdateParameters(string[] parameterData)
        {
            if (parameterData.Length == 1)
                HandleNonCommaSeperatedData(parameterData);
            else
                HandleCommaSeperatedData(parameterData);
        }

        private void HandleCommaSeperatedData(string[] parameterData)
        {

            switch (parameterData[0])
            {
                case "$GPGGA":
                    ParseTimePostionFixData(parameterData);
                    break;
                case "$GPGLL":
                    ParseLatLongTimeStatusData(parameterData);
                    break;
                case "$GPGSA":
                    ParseOpModePosSolutionData(parameterData);
                    break;
                case "$GPGSV":
                    ParseSatInViewEleAzSNRData(parameterData);
                    break;
                case "$GPMSS":
                    ParseSignalToNoiseSignalStrengthData(parameterData);
                    break;
                case "$GPRMC":
                    ParseTimeDatePositionSpeedData(parameterData);
                    break;
                case "$GPVTG":
                    ParseCourseSpeedInformationToGroundData(parameterData);
                    break;
                case "$GPZDA":
                    ParsePPSTimingMessageData(parameterData);
                    break;

            }
            //    LogGPSData(parameterData[DATA_DATE_INDEX], parameterData[DATA_TIME_INDEX],
            //               parameterData[DATA_LONGITUDE_INDEX], parameterData[DATA_LATITUDE_INDEX],
            //               parameterData[DATA_ALTITUDE_INDEX]);
        }

        private void ParsePPSTimingMessageData(string[] parameterData)
        {
            // throw new Exception("The method or operation is not implemented.");
        }

        private void ParseCourseSpeedInformationToGroundData(string[] parameterData)
        {
           if (parameterData.Length == 10)
           {
              pcHeading.ParameterValue = parameterData[1];
               pcSpeed.ParameterValue = GetMPHFromKMPH(parameterData[7]);

              double speed = 0;

              if (double.TryParse(pcSpeed.ParameterValue, out speed))
              {
                  //ledSpeed.Value = speed;
                  lblSpeed.Text = speed.ToString();
                  rgSpeed.Value = speed;
              }

              double heading = 0;

              if (speed >= 1 && double.TryParse(pcHeading.ParameterValue, out heading))
              {
                  //ledHeading.Value = heading;
                  lblHeading.Text = heading.ToString();
                  rgHeading.Value = heading;
              }
              else
              {
                  rgHeading.Value = 0;
                  lblHeading.Text = "0";
              }
           }
        }

        private static string GetMPHFromKMPH(string strSpeedInKM)
        {
            double speedKPH = 0;
            double speedMPH = 0;

            if (double.TryParse(strSpeedInKM, out speedKPH))
                speedMPH = speedKPH * 0.621371192;

            return speedMPH.ToString();
        }

        private void ParseTimeDatePositionSpeedData(string[] parameterData)
        {
            if (parameterData.Length == 13)
            {
                string last = pcDataValid.ParameterValue;
                pcDataValid.ParameterValue = (parameterData[2] == "A" ? "Yes" : "No");

                if (last != pcDataValid.ParameterValue)
                    lbData.Items.Add("Data Valid: " + pcDataValid.ParameterValue);

                 ParseValidTimeDateData(parameterData);
            }
        }

       private void ParseValidTimeDateData(string[] parameterData)
       {
          if (parameterData[2] == "A")
          {
             Utilities utils = new Utilities();

             pcTime.ParameterValue       = utils.FormatTime(parameterData[1]);
             pcDate.ParameterValue       = utils.FormatDate(parameterData[9]);
             pcLatitude.ParameterValue   = parameterData[3];
             pcNorthSouth.ParameterValue = parameterData[4];
             pcLongitude.ParameterValue  = parameterData[5];
             pcEastWest.ParameterValue   = parameterData[6];

#if GOOGLE_EARTH_ENABLED
             GoogleEarthGotoPoint(utils);
#endif
          }
       }

#if GOOGLE_EARTH_ENABLED
       private void GoogleEarthGotoPoint(Utilities utils)
       {
          string strLatitude = utils.ConvertLatitude(pcLatitude.ParameterValue, pcNorthSouth.ParameterValue);
          string strLongitude = utils.ConvertLongitude(pcLongitude.ParameterValue, pcEastWest.ParameterValue);

          Debug.WriteLine("Lat: " + strLatitude + " Long: " + strLongitude);

          double latitude = 0;
          double longitude = 0;
          double altitude = 0;

          if (double.TryParse(strLatitude, out latitude) &&
             double.TryParse(strLongitude, out longitude) &&
             double.TryParse(pcAltitude.ParameterValue, out altitude))
          {
              bgAltitude.Value = altitude;

             geCntrl.GotoPoint(longitude, latitude, altitude);
         }
       }
#endif

        private void ParseSignalToNoiseSignalStrengthData(string[] parameterData)
        {
            // throw new Exception("The method or operation is not implemented.");
        }

        private void ParseSatInViewEleAzSNRData(string[] parameterData)
        {
            if (parameterData.Length == 20)
            {

            }
        }

        private void ParseOpModePosSolutionData(string[] parameterData)
        {
            if (parameterData.Length == 18)
            {
                //if (parameterData[2] == "1")
                //    pcDataValid.ParameterValue = "No1";
                //else if(parameterData[2] == "2" || parameterData[2] == "3")
                //    pcDataValid.ParameterValue = "Yes";
                //else
                //    pcDataValid.ParameterValue = "No";
            }
            // throw new Exception("The method or operation is not implemented.");
        }

        private void ParseLatLongTimeStatusData(string[] parameterData)
        {
            // throw new Exception("The method or operation is not implemented.");
        }

       private ulong curTime = 0;

        private void ParseTimePostionFixData(string[] parameterData)
        {
            if (parameterData.Length == 15)
            {
                Utilities utils = new Utilities();

                pcSatellites.ParameterValue = parameterData[7];
                pcAltitude.ParameterValue   = utils.ConvertMetersToFeet(parameterData[9]);

                double alt = 0;

                if (double.TryParse(pcAltitude.ParameterValue, out alt))
                {
#if _TEST
                   scatterGraph1.PlotXY(curTime, alt);
#endif
                   curTime++;
                }
            }
        }


#if GOOGLE_EARTH_ENABLED
        private void LogGPSData(string date, string time, string longitude, string latitude, string altitude)
        {
            Debug.WriteLine("LogGPSData");
            latLongAltArray.Add(date + ", " + time + ", " + longitude + ", " + latitude + ", " + altitude);
        }
#endif

        private void HandleNonCommaSeperatedData(string[] parameterData)
        {
            if ((parameterData[0].Split(new char[] { ':' }).Length > 1))
                lastReceivedIndex = 2;
            else if (lastReceivedIndex == -1)
                return;

            switch (lastReceivedIndex)
            {
                case DATA_VALID_INDEX:
                    pcDataValid.ParameterValue = ((int.Parse(parameterData[0]) == 0) ? "No" : "Yes");
                    break;
                case DATA_SATELLITES_INDEX:
                    pcSatellites.ParameterValue = parameterData[0];
                    break;
                case DATA_TIME_INDEX:
                    pcTime.ParameterValue = parameterData[0];
                    break;
                case DATA_DATE_INDEX:
                    pcDate.ParameterValue = parameterData[0];
                    break;
                case DATA_LATITUDE_INDEX:
                    pcLatitude.ParameterValue = parameterData[0];
                    break;
                case DATA_LONGITUDE_INDEX:
                    pcLongitude.ParameterValue = parameterData[0];
                    break;
                case DATA_ALTITUDE_INDEX:
                    pcAltitude.ParameterValue = parameterData[0];
                    break;
                case DATA_SPEED_INDEX:
                    pcSpeed.ParameterValue = parameterData[0];
                    break;
                case DATA_HEADING_INDEX:
                    pcHeading.ParameterValue = parameterData[0];
                    break;
            }

            if (lastReceivedIndex < 8)
                lastReceivedIndex++;
            else
                lastReceivedIndex = 0;
        }

        private void tsbtnSetup_Click(object sender, EventArgs e)
        {
            SetupForm sf = new SetupForm(serialPort1);

            DialogResult dr = sf.ShowDialog();

            if (dr == DialogResult.OK)
            {
                serialPort1 = sf.GetUpdatedSerialPort();
                if (serialPort1 != null)
                {
                    Properties.Settings.Default.ComPort = serialPort1.PortName;
                    Properties.Settings.Default.BaudRate = serialPort1.BaudRate;
                    Properties.Settings.Default.DataBits = serialPort1.DataBits;
                    Properties.Settings.Default.Save();
                }
            }
        }

        private void tsbtnConnect_Click(object sender, EventArgs e)
        {
            tsbtnSetup.Enabled = false;
            connectDisconnect();

            if (tsbtnConnect.Text == "Connect")
                tsbtnSetup.Enabled = true;
        }

        private void tsbtnSave_Click(object sender, EventArgs e)
        {
#if GOOGLE_EARTH_ENABLED
            DialogResult dr = MessageBox.Show(this, "Save In KML Format?\nPress 'No' to save as Excel.", "Save Question", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);

            if (dr == DialogResult.No)
            {
                //ExcelSave es = new ExcelSave(this);
                //es.SaveExcelFile();
            }
            else if (dr == DialogResult.Yes)
            {
                string filename = SaveKMLData();
                ageClass.OpenKmlFile(filename, 0);
            }
#else
          SaveFileDialog sfd = new SaveFileDialog();

          sfd.Filter     = "Text (*.txt) | *.txt";
          sfd.DefaultExt = ".txt";

          if (sfd.ShowDialog() == DialogResult.OK)
          {
              cbPauseData.Checked = true;
              string saveFile = sfd.FileName;

              StreamWriter sw = new StreamWriter(saveFile, false);

              ListBox.ObjectCollection objc = lbData.Items;

              foreach(string str in objc)
                 sw.WriteLine(str);

              cbPauseData.Checked = false;
          }
#endif
        }

#if GOOGLE_EARTH_ENABLED
        private string SaveKMLData()
        {
            string filename = AskForSaveFile();

            if (filename == "")
                return "";

            xmlParsing parser = new xmlParsing();


            parser.saveKMLData(filename, "Path", latLongAltArray, new ProgressBar(), true);
            return filename;
        }

        private string AskForSaveFile()
        {
            SaveFileDialog sfd = new SaveFileDialog();
            sfd.Filter = "KML File(*.kml)|*.kml|All Files (*.*)|*.*";
            sfd.FileName = "test.kml";

            if (sfd.ShowDialog(this) == DialogResult.OK)
                return sfd.FileName;

            return "";
        }
#endif

        private void tsbtnAbout_Click(object sender, EventArgs e)
        {
            AboutBox1 ab = new AboutBox1();

            ab.Show();
        }

        private void tsbtnClear_Click(object sender, EventArgs e)
        {
#if GOOGLE_EARTH_ENABLED
            geCntrl.TestGetFeature();
#endif
            lbData.Items.Clear();
        }

        private void tslConvertNEMA_Click(object sender, EventArgs e)
        {
            SaveFileDialogKLM sfdk = new SaveFileDialogKLM();

            sfdk.ShowDialog();
        }

        private void tpControls_Click(object sender, EventArgs e)
        {

        }

        double tickerLat  = -83.1523233333333;
        double tickerLong = 42.679185;
        double tickerAlt  = 788.71391196;

        bool loaded    = false;
        bool firstLoad = true;

        private void timer1_tick(object sender, EventArgs e)
        {
            if (firstLoad)
            {
                timer1.Interval = 1000;
                firstLoad = false;
                return;
            }

            if (!loaded)
            {
                loaded = true;

            }
            else
            {
                xmlParsing xml = new xmlParsing();

                xml.saveKMLNetworkLinkCoordinates(tickerLat, tickerLong, tickerAlt);
                Debug.WriteLine("Lat: " + tickerLat.ToString() + " Long: " + tickerLong.ToString() + " Alt: " + tickerAlt.ToString());

                tickerLat -= 0.001;
            }
        }

       private void ledSpeed_Click(object sender, EventArgs e)
       {

       }
    }
}